import numpy as np
from copy import deepcopy
from cmds import Cluster, read, write_xsd


class Polyhedron:

    def __init__(self, p):
        if type(p) == int:
            self.at = [[] for _ in range(p)] #邻接表
        elif type(p) == list:
            self.at = p
        else:
            print('parameter error')

    def __repr__(self):
        """转字符串"""
        s = '%d\n'%len(self.at)
        for vb in self.at:
            s += ' '.join([str(x) for x in vb]) + '\n'
        return s
        
    @classmethod
    def from_bin(cls, f):
        '''从plantri生成的二进制文件中读取1个结构'''
        try:
            n = ord(f.read(1))
        except:
            return None
        p = cls(n)
        for i in range(n):
            while True:
                v = ord(f.read(1))
                if v == 0:
                    break
                p.at[i].append(v-1)
        return p
        
    @classmethod
    def from_bin_all(cls, file_name):
        """读取plantri生成的二进制文件中的全部结构"""
        f = open(file_name, 'rb')
        f.read(18) #>>planar_code le<<
        result = []
        while True:
            p = cls.from_bin(f)
            if p is None:
                break
            result.append(p)
        return result

    @classmethod
    def from_txt(cls, f):
        """从邻接表文件中读取多面体。多面体之间无空格"""
        line = f.readline()
        if line == '':
            return None
        n = int(line.split()[0])
        p = cls(n)
        for i in range(n):
            p.at[i] = [int(_) for _ in f.readline().split()]
        return p

    def save(self, f):
        f.write(str(self))

    def degree(self, v):
        return len(self.at[v])

    def next(self, first, second):
        '''根据点first及其邻接点second寻找逆时针的下一个点'''
        idx = self.at[second].index(first)
        if idx != len(self.at[second]) - 1:
            return self.at[second][idx+1]
        else:
            return self.at[second][0]

    def to_bonds(self):
        '''邻接表转键'''
        bonds = []
        for v,vt in enumerate(self.at):
            for v2 in vt:
                if v<v2:
                    bonds.append((v,v2))
        return bonds

    def to_adj(self):
        '''转邻接矩阵'''
        adj = np.zeros((len(self.at), len(self.at)))
        for v,vt in enumerate(self.at):
            for v2 in vt:
                adj[v,v2] = 1
        return adj

    def get_face(self, first, second):
        face = [first,second]
        while face[-1] != face[0]:
            face.append(self.next(face[-2], face[-1]))
        return face[:-1]

    def dist(self):
        '''求两两距离矩阵。Floyd算法'''
        n = len(self.at)
        d = np.ones((n,n), dtype='int') * n
        for v in range(n):
            for u in self.at[v]:
                d[u,v] = d[v,u] = 1
            d[v,v] = 0
        for k in range(n): #考虑i,j两点经过k的距离
            for i in range(n):
                for j in range(n):
                    d[i,j] = min(d[i,j], d[i,k] + d[k,j])
        return d

    @staticmethod
    def cycle_hash(cycle):
        n = len(cycle)
        max_h = 0
        for i in range(n):
            h = 0
            for j in range(n):
                h = (h*1315423911 + cycle[(i+j+1)%n]) & 0xffffffffffffffff
            max_h = max(h, max_h)
        return max_h

    def __init_vertex_hash(self):
        return [len(conn) for conn in self.at]

    def _hash_aggregate(self, vh):
        '''邻接点和邻接面的hash跟中心点hash聚合'''
        vh2 = []
        for v,conn in enumerate(self.at):
            vertex_hash, face_hash = [], []
            for u in conn:
                vertex_hash.append(vh[u])
                cycle = self.get_face(v,u)
                face_hash.append(self.cycle_hash(cycle))
            vh2.append((self.cycle_hash(vertex_hash) ^ self.cycle_hash(face_hash)) * vh[v] & 0xffffffffffffffff)
        return vh2

    def __hash__(self):
        '''聚合一次，跟镜面hash取较大的'''
        vh = self.__init_vertex_hash()
        vh2a = self._hash_aggregate(vh)

        m = self.mirror()
        vh = m.__init_vertex_hash()
        vh2b = m._hash_aggregate(vh)
        return max(sum(vh2a), sum(vh2b)) & 0xffffffffffffffff
    
    @staticmethod
    def read_vertex_hash_table(file_name):
        f = open(file_name)
        hash, permut = [], []
        for line in f:
            line = line.split()
            hash.append(int(line[0]))
            permut.append(line[1][:-1])
        return dict(zip(hash, permut)) #转存成字典方便查找

    def dual(self):
        '''对偶多面体
        返回：对偶多面体，有向边到面的映射'''
        def mark_hash(first, second, face=None):
            '''递归标记有向边对应的面'''
            nonlocal nface
            if (first, second) not in edge2face:
                if face is None:
                    face = nface
                    nface += 1
                edge2face[(first, second)] = face
                return mark_hash(second, self.next(first,second), face)
            else:
                return edge2face[(first, second)]

        n = len(self.at)
        edge2face = {} #有向边到面的映射
        p = type(self)(5*n) #最大可能顶点数：n*最大面数/2
        nface = 0 #总面数
        for v,vt in enumerate(self.at):
            for v2 in vt:
                face = mark_hash(v, v2)
                if len(p.at[face]) == 0: #该面还未写邻接表
                    first,second = v,v2
                    while True: #遍历v,v2面
                        p.at[face].append(mark_hash(second,first))
                        first, second = second, self.next(first,second)
                        if first == v:
                            break
        del p.at[nface:]
        return p, edge2face

    def mirror(self):
        m = deepcopy(self)
        m.at = [_[::-1] for _ in self.at]
        return m

    @classmethod
    def from_cluster(cls, c):
        """从团簇中获取多面体"""
        def 删除同种键():
            for i in range(len(c)):
                for j in range(i):
                    if c[i].elem == c[j].elem and j in p.at[i]:
                        p.at[i].remove(j)
                        p.at[j].remove(i)
                        
        c.center()
        p = cls(len(c))
        if hasattr(c, 'adj'):
            p.at = [np.where(c.adj[i]>0)[0] for i in range(len(c))]
        else:
            p.at = c.get_connections()
        if hasattr(cls, 'alternate_elem'): #BN
            删除同种键()

        for i,conn in enumerate(p.at): #将i的所有邻接点conn按逆时针顺序排列
            x = np.empty((len(conn),3), dtype='float') #邻接点坐标矩阵
            for j,ij in enumerate(conn):
                x[j,0], x[j,1], x[j,2] = c[ij].x, c[ij].y, c[ij].z
            x = x - np.mean(x,axis=0)
            u,s,v = np.linalg.svd(x.T.dot(x))
            y = x.dot(u)
            theta = np.angle(y[:,0] + np.complex('j')*y[:,1], deg=True)
            if (u[:,2].dot(np.array([c[i].x,c[i].y,c[i].z])) < 0) ^ \
                (np.linalg.det(u) < 0): #确保拟合邻接点平面的法向朝外
                p.at[i] = [__[1] for __ in sorted(list(zip(theta,conn)), key=lambda _:-_[0])]
            else:
                p.at[i] = [__[1] for __ in sorted(list(zip(theta,conn)), key=lambda _:_[0])]
        return p

    def embeded3d(self):
        D = self.dist()
        n = len(self.at)
        J = np.eye(n) - np.ones((n, n)) / n
        B = -0.5 * J @ D ** 2 @ J
        L, U = np.linalg.eig(B)
        c = U[:, :3] @ np.diag(L[:3] ** 0.5)
        return c
        
    def to_cluster(self, file_name, elem='C', dual=False):
        '''三角多面体生成团簇（并保存）'''
        if dual:
            q,_ = self.dual()
        else:
            q = self
        coord = q.embeded3d()
        c = Cluster(coord, [elem] * coord.shape[0])
        bonds = q.to_bonds()
        c.clean(bonds=bonds)
        if file_name:
            write_xsd(c, file_name, bonds)
        return c


def plantri2tri(in_name, out_name):
    """将plantri生成的二进制文件保存成邻接表格式"""
    f = open(in_name, 'rb')
    f.read(18) #>>planar_code le<<
    g = open(out_name, 'w')
    count = 0
    while True:
        p  = Polyhedron.from_bin(f)
        if p is None:
            break
        count += 1
        g.write(str(p))


if __name__ == '__main__':
    f = open(r'D:\360yunpan\Cluster\topology\BN\三角多面体\tri26_iso.txt')
    p = Polyhedron.from_txt(f)
    p.to_cluster('clean_dbg.xsd', dual=True)